---
title: Working with option lists
description: Managing ambiguity in option list types
---

import { DemoLink } from '@site/src/components/DemoLink';
import Details from '@theme/Details';

%importmd ../\_ts_admonition.md

Option list props in React Query Builder—such as [`fields`](../components/querybuilder#fields), [`combinators`](../components/querybuilder#combinators), and [`operators`](../components/querybuilder#operators)—use the [`OptionList`](../typescript#option-lists) type. `OptionList` is a union type supporting two array formats: `Option[]` and `OptionGroup[]`. While this design provides flexibility, it introduces complexity when consuming option list props in custom subcomponents.

This guide offers strategies for managing the inherent ambiguity in option list TypeScript types.

:::info

- `Option` objects have `label`, `name`, and `value` properties, all of which extend `string`.
- `OptionGroup` objects have a `label` property along with an `options` property which is an array of `Option` objects.

This mirrors the `children` prop structure of `<select>` elements, which can contain either `<option>` elements directly or `<optgroup>` elements with nested `<option>` lists.

:::

## An incorrect assumption

You may have found this page after seeing a TypeScript error message similar to this:

```
Property 'name' does not exist on type 'FullOption<string> | OptionGroup<FullOption<string>>'.
  Property 'name' does not exist on type 'OptionGroup<FullOption<string>>'. ts(2339)
```

This typically occurs when treating option list elements as guaranteed `Option` types. For example, mapping through the list to access the `name` property:

```tsx
const ListAllOptionNames = (props: ValueSelectorProps) => {
  return <div>{props.options.map(opt => opt.name).join(', ')}</div>;
  //                                        ^^^^ error
};
```

While it seems logical that an option list passed to `QueryBuilder` as `Option[]` would remain `Option[]` in subcomponents, React Query Builder's type generics only infer the option types within the list, not the list structure itself.

:::tip

All options in subcomponent props are guaranteed to include both `name` and `value` properties, even if the original `QueryBuilder` prop omitted one of them.

As an example, consider this `fields` array:

```ts
const fields: Field[] = [
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
];
```

When this array is assigned to the [`fields` prop](../components/querybuilder#fields), the `fieldSelector` component will receive the same array but with each option object augmented with a `value` property equivalent to the original `name`:

```tsx
const MyFieldSelector = (props: FieldSelectorProps) => {
  console.log(props.options); // =>
  // [
  //   { name: 'firstName', value: 'firstName', label: 'First Name' },
  //   { name: 'lastName', value: 'lastName', label: 'Last Name' }],
  // ]
  return <ValueSelector {...props} />;
};

const App = () => (
  <QueryBuilder fields={fields} controlElements={{ fieldSelector: MyFieldSelector }} />
);
```

**If `name` and `value` differ for a given option, `value` takes precedence.**

:::

## Workarounds

Several approaches can handle this ambiguity. One option is casting option list props to `Option[]` using the `as` keyword. While this may work if your option list is definitely `Option[]`, the `as` keyword only suppresses TypeScript errors without affecting runtime behavior. This essentially misleads the TypeScript compiler and prevents proper type checking, making it an unrecommended approach.

```tsx
const MyComponent(props: ValueSelectorProps) => {
  return <div>{(props.options as Option[]).map(opt => opt.name).join(', ')}</div>;
  //                          ^^^^^^^^^^^ avoids TypeScript error; may have issues during execution
};
```

:::info

All [default option lists](../utils/misc#defaults) (`defaultCombinators`, `defaultOperators`, etc.) are type `Option[]`.

:::

A better solution uses the [`isOptionGroupArray`](#isoptiongrouparray) type guard to determine the option list type. This approach avoids the deception of type casting while enabling different behaviors for `Option[]` versus `OptionGroup[]` arrays.

```tsx
const MyComponent(props: ValueSelectorProps) => {
  if (isOptionGroupArray(props.options)) {
    return <div>{props.options.flatMap(og => og.options).map(opt => opt.name).join(', ')}</div>;
  }
  return <div>{(props.options).map(opt => opt.name).join(', ')}</div>;
};
```

## Utilities

Several utility functions simplify working with option list props without requiring type guards or `as` casting.

### `getOption`

```ts
function getOption(arr: OptionList, identifier: string): Option;
```

Retrieves the complete option object from an option list using the given identifier (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats.

#### Examples

```ts
getOption(
  [
    { name: 'firstName', label: 'First Name' },
    { name: 'lastName', label: 'Last Name' },
  ],
  'lastName'
);
// => { name: 'lastName', label: 'Last Name' }

getOption(
  [
    { label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },
    { label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },
  ],
  'lastName'
);
// => { name: 'lastName', label: 'Last Name' }
```

### `getFirstOption`

```ts
function getFirstOption(arr: OptionList): Option;
```

Returns the identifier value (`name` or `value`) of the first `Option` in the list, supporting both `Option[]` and `OptionGroup[]` formats.

`QueryBuilder` uses this function to establish default values for option lists in new rules and groups when no other default determination method is available.

#### Examples

```ts
getFirstOption([
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
]);
// => 'firstName'

getFirstOption([
  { label: 'First', options: [{ name: 'firstName', label: 'First Name' }] },
  { label: 'Last', options: [{ name: 'lastName', label: 'Last Name' }] },
]);
// => 'firstName'
```

### `toOptions`

```ts
function toOptions(arr: OptionList): ReactElement;
```

Creates `<option>` elements for `Option` arrays or `<optgroup>` elements for `OptionGroup` arrays. Designed for use as the `children` prop of `<select>` elements, as implemented in [`ValueSelector`](../components/valueselector).

:::info

Some of the [compatibility packages](../compat) implement their own `toOptions` method that generates "option" elements appropriate for their respective style library.

:::

#### Usage

```tsx
const MyComponent(props: ValueSelectorProps) => {
  return (
    <select value={props.value} onChange={e => props.handleOnChange(e.target.value)}>
      {toOptions(props.options)}
    </select>
  )
}
```

<Details summary= "Examples">

#### `Option[]` example

```tsx
toOptions([
  { value: 'firstName', label: 'First Name' },
  { value: 'lastName', label: 'Last Name' },
]);
// yields (approximately):
[
  <option key={'firstName'} value={'firstName'}>
    First Name
  </option>,
  <option key={'lastName'} value={'lastName'}>
    Last Name
  </option>,
];
```

#### `OptionGroup[]` example

```tsx
toOptions([
  { label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },
  { label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },
]);
// yields (approximately):
[
  <optgroup key={'First'} label={'First'}>
    <option key={'firstName'} value={'firstName'}>
      First Name
    </option>
  </optgroup>,
  <optgroup key={'Last'} label={'Last'}>
    <option key={'lastName'} value={'lastName'}>
      Last Name
    </option>
  </optgroup>,
];
```

</Details>

### `toFlatOptionArray`

```ts
function toFlatOptionArray(arr: any): boolean;
```

Converts `OptionGroup` arrays to flattened `Option` arrays using [`Array.prototype.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) when grouped structures are unsuitable, while leaving `Option` arrays unchanged. The result is deduplicated using [`uniqByIdentifier`](#uniqbyidentifier).

### `isOptionGroupArray`

```ts
function isOptionGroupArray(arr: any): boolean;
```

A type guard that distinguishes between the two array types in `OptionList`. When this returns `true`, the array is `OptionGroup[]`, meaning actual options are nested within each group's `options` property.

<Details summary= "Examples">

```ts
isOptionGroupArray([
  { value: 'firstName', label: 'First Name' },
  { value: 'lastName', label: 'Last Name' },
]);
// => false

isOptionGroupArray([
  { label: 'First', options: [{ value: 'firstName', label: 'First Name' }] },
  { label: 'Last', options: [{ value: 'lastName', label: 'Last Name' }] },
]);
// => true
```

</Details>

### `uniqOptList`

```ts
function uniqOptList(arr: any): boolean;
```

Removes duplicate options from an `OptionList` by comparing identifier properties (`name` or `value`), working with both `Option[]` and `OptionGroup[]` formats.

### `uniqByIdentifier`

```ts
function uniqByIdentifier(arr: any): boolean;
```

Removes duplicate options from an `Option` array by comparing identifier properties (`name` or `value`).
